import sortBy from 'lodash-es/sortBy.js';
import md5 from './spark-md5.js';
import storage from '../utils/storage.js';

const API_CACHE_STORE = {};
export const STORE_PREFIX_KEY = '_request_cache_';

if (import.meta.env.DEV) {
  window.__API_CACHE_STORE_ = API_CACHE_STORE;
}

class ReadyPromise {
  constructor() {
    let _resolve = null;
    let _reject = null;
    let callbacks = { ok: [], fail: [] };

    this.state = new Promise((resolve, reject) => {
      _resolve = resolve;
      _reject = reject;
    });
    this.onReady = function (callback) {
      if (typeof callback === 'function') callbacks.ok.push(callback);
    };
    this.onFail = function (callback) {
      if (typeof callback === 'function') callbacks.fail.push(callback);
    };
    this.ready = function (res) {
      typeof _resolve === 'function' && _resolve(res);
      if (callbacks && callbacks.ok && callbacks.ok.length) {
        callbacks.ok.forEach((callback) => callback(res));
      }
    };
    this.fail = function (e) {
      typeof _reject === 'function' && _reject(e);
      if (callbacks && callbacks.fail && callbacks.fail.length) {
        callbacks.fail.forEach((callback) => callback(e));
      }
    };
    this.run = async (handle) => {
      try {
        let res = await handle();
        this.ready(res);
      } catch (e) {
        this.fail(e);
      }
    };
    this.destroy = function () {
      callbacks.ok.length = 0;
      callbacks.fail.length = 0;
      _reject();
      _reject = _resolve = callbacks = null;
    };
  }
}

export function getRequestUniKey(params, prefix) {
  let uniKey = '';
  if (Array.isArray(params)) {
    params = params.map((item) => getRequestUniKey(item));
    uniKey = md5.hash(JSON.stringify(params));
  } else if (params && typeof params === 'object') {
    const keys = sortBy(Object.keys(params));
    params = keys.reduce((res, key) => {
      res.push({ [key]: params[key] });
      return res;
    }, []);
    uniKey = md5.hash(JSON.stringify(params));
  } else {
    uniKey = md5.hash(String(params));
  }
  return (typeof prefix === 'string' ? prefix : '') + uniKey;
}

export class CacheRequester {
  constructor(api, options) {
    const opt = {
      defaultValue: [],
      context: undefined,
      useLocalStorage: false,
      useSessionStorage: false,
      force: false,
      onError: null,
      isDataExist: (data) => typeof data !== 'undefined',
    };
    this.baseUniKey = md5.hash(Function.prototype.toString.call(api));
    this.options = Object.assign({}, opt, options);
    this.dataStore = {};
    this.promise = {};
    this.loading = {};
    this.api = api;
  }
  setContext(context) {
    this.options.context = context;
    return this;
  }
  setStoreData(cacheKey, data) {
    if (this.options.useLocalStorage === true) {
      storage.local.setItem(cacheKey, data);
    } else if (this.options.useSessionStorage === true) {
      storage.session.setItem(cacheKey, data);
    } else {
      this.dataStore[cacheKey] = data;
    }
  }
  getStoreData(cacheKey) {
    if (this.options.useLocalStorage === true) {
      return storage.local.getItem(cacheKey);
    } else if (this.options.useSessionStorage === true) {
      return storage.session.getItem(cacheKey);
    } else {
      return this.dataStore[cacheKey];
    }
  }
  clear() {
    Object.keys(this.promise).forEach((key) => {
      this.promise[key].destroy();
      delete this.loading[key];
      delete this.promise[key];
      delete this.dataStore[key];
      storage.local.removeItem(key);
      storage.session.removeItem(key);
    });
  }
  run(params) {
    let cacheKey = STORE_PREFIX_KEY + this.baseUniKey + getRequestUniKey(params, '_');
    // console.log('CacheRequester cacheKey: ', cacheKey);
    let data = this.getStoreData(cacheKey);
    if (this.options.force === true) {
      delete this.options.force;
      data = undefined;
    }
    if (!this.promise[cacheKey]) {
      this.promise[cacheKey] = new ReadyPromise();
    }
    if (this.options.isDataExist(data)) {
      this.promise[cacheKey].ready(data);
      return this.promise[cacheKey].state;
    }
    if (this.loading[cacheKey] === true) {
      return this.promise[cacheKey].state;
    }
    this.loading[cacheKey] = true;
    this.promise[cacheKey].onFail((e) => {
      delete this.loading[cacheKey];
      if (typeof this.options.onError === 'function') {
        this.options.onError(e);
      }
    });
    this.promise[cacheKey].onReady((res) => {
      this.setStoreData(cacheKey, res);
      delete this.loading[cacheKey];
    });
    this.promise[cacheKey].run(async () => {
      return await this.api.call(this.options.context, params);
    });
    return this.promise[cacheKey].state;
  }
}

/* 推荐优先使用 useCacheRequest,方便重置缓存 */
export function useRequest(api, options) {
  return new CacheRequester(api, options);
}

/* 推荐优先使用 useCacheRequest,方便重置缓存 */
export async function useCacheRequest(api, params, options) {
  const uniApiKey = md5.hash(Function.prototype.toString.call(api));
  if (!API_CACHE_STORE[uniApiKey]) {
    API_CACHE_STORE[uniApiKey] = useRequest(api, options);
  }
  if (options && typeof options === 'object') {
    API_CACHE_STORE[uniApiKey].options = Object.assign({}, API_CACHE_STORE[uniApiKey].options, options);
  }
  return await API_CACHE_STORE[uniApiKey].run(params);
}

export function clearRequestCache() {
  Object.keys(API_CACHE_STORE).forEach((key) => {
    API_CACHE_STORE[key].clear();
    delete API_CACHE_STORE[key];
  });
  let regexp = new RegExp('^' + STORE_PREFIX_KEY);
  Object.keys(window.sessionStorage).forEach((key) => {
    if (regexp.test(key)) window.sessionStorage.removeItem(key);
  });
  Object.keys(window.localStorage).forEach((key) => {
    if (regexp.test(key)) window.localStorage.removeItem(key);
  });
}
